[CS] 비동기와 논블로킹의 개념 차이
비동기와 논블로킹의 개념적 차이
요즘 내가 가장 흥미롭게 생각하는 개념이 바로 비동기와 논블로킹이다. 나에게 기존의 비동기 개념은 논블로킹과 같았는데 최근에 Apache Livy의 비동기 API와 PSQL 비동기 라이브러리인 asyncpg로 API를 개발하면서 이 두 가지의 개념이 근본적으로 다르다는 것을 알게 되었다. 이 개념이 아직도 조금은 혼란스럽지만 현재까지의 깨달음을 적어보고자 한다.
동기와 비동기
동기 (Synchronous)
동기와 비동기의 개념은 코드 실행 순서와 결과를 받는 순서가 일치하냐 아니냐에 대한 관점이다.
동기 방식에서는 작업들이 순차적으로 실행되며 한 작업이 완료되기 전까지는 다음 작업이 시작되지 않는다.
코드의 실행 순서가 작업의 실제 처리 순서와 반드시 일치할 수 밖에 없는 게 동기 방식이다.
작업이 순차적으로 실행되므로 프로그램의 흐름을 이해하고 디버깅하기가 비교적 쉽고 처리 순서가 명확하므로 흐름이 직관적인 게 특징이다.
하지만 한 작업이 끝날 때까지 다음 작업을 대기해야 하기 때문에 시스템 자원 활용성 측면에서 비동기 방식보다 현저히 떨어질 수 있다.
비동기 (Asynchronous)
그에 반해 비동기는 한 작업이 끝나기를 기다리지 않고 다음 작업을 시작할 수 있다. 작업이 완료되면 여기에 대한 알림이나 콜백을 통해 결과를 처리한다. 작업들이 서로 기다리지 않기 때문에 시스템 자원을 동기 방식보다 효율적으로 활용할 수 있지만 비동기 방식을 사용하면 프로그램 설계가 복잡해진다. 여러 비동기 작업들 사이의 상호 의존성을 고려해야하는 관리 포인트가 생기고 비동기 작업의 결과 순서를 예측하기 어렵기 때문에 디버깅도 어렵다.
블로킹과 논블로킹
블로킹 (Blocking)
블로킹과 논블로킹은 어떤 작업이 다른 작업에 대해 얼마나 제어권을 유지할 수 있는지에 대한 관점이다. 블로킹 방식에서는 특정 작업(대부분의 경우 입출력 작업)을 실행하는 동안 해당 작업을 요청한 프로세스나 스레드가 다른 작업을 수행할 수 없게 된다.
논블로킹 (Non-blocking)
논블로킹 방식에서는 작업 요청이 즉시 완료되지 않더라도 프로세스나 스레드가 대기하지 않고 즉시 제어권을 반환받아 다른 작업을 계속 수행할 수 있다. 특히 네트워크 요청이나 파일 I/O와 같은 시간이 많이 소요되는 작업에 유용하다. 프로세스가 블록되지 않으므로 시스템 자원을 더 효율적으로 활용할 수 있고 사용자 인터페이스 같은 곳에서 더 빠르게 반응을 해줄 수 있다. 논블로킹은 특히 비동기 프로그래밍과 결합될 때 더욱 복잡해진다. 논블로킹 작업 상태를 추적하거나 관리하기 어렵기 때문에 디버깅이 어렵다.
Apache Livy와 PSQL asyncpg의 비동기 구성 차이
Apache Livy의 비동기 구성
Apache Livy는 Spark 클러스터에서 인터랙티브하게 Spark 작업을 제출하고 관리하기 위해 만들어진 서비스다. Livy API는 비동기 논블로킹 방식으로 설계되었기 때문에 요청이 서버에 전송된 후 서버가 요청을 처리하는 동안 클라이언트가 다른 작업을 계속할 수 있게 된다. Livy에서는 작업 제출과 작업 상태 확인, 작업 결과 가져오기가 별도의 요청으로 처리 된다.
asyncpg의 비동기 구성
asyncpg는 PostgreSQL 비동기 데이터베이스 작업을 위한 파이썬 라이브러리다. asyncpg는 파이썬의 asyncio 프레임워크를 사용하여 비동기 구현이 되는데 asyncpg의 작업도 엄밀히 말하면 논블로킹이다. 하나의 쿼리가 실행되는 동안 다른 쿼리 작업도 실행할 수 있다는 뜻이다. 하지만 일반적으로 데이터베이스 작업은 쿼리를 실행하고 결과를 받아야하는 형식이기 때문에 작업 요청과 결과 요청이 Livy처럼 분리되어 처리되지는 않는다. 하나의 메인 루틴 내에서 여러 서브 루틴들이 각각 비동기 논블로킹 방식으로 작동하지만 결국엔 모든 작업의 결과를 받을 때까지 기다려야 한다.
Livy를 활용해서 API를 만든다면 요청과 결과를 쉽게 분리할 수 있기 때문에 클라이언트 관점에서 내 API는 비동기 논블로킹이 된다.
하지만 asyncpg로 API를 만든다면 내부적으로는 비동기 논블로킹 방식으로 처리되지만 클라이언트 관점에서 해당 API 자체는 ‘블로킹’처럼 느껴질 수 있다.
API가 모든 데이터베이스 작업이 완료될 때까지 결과를 반환하지 않기 때문이다.
그래서 asyncpg로 만든 API를 클라이언트 관점에서 비동기 논블로킹이라고 생각되게끔 하려면 서버 쪽에서 RabbitMQ 같은 메시지큐를 사용하여 작업 요청과 결과 반환을 분리하는 등의 추가적인 설계가 필요하다. 일반적으로 단순 데이터베이스 작업을 하는 API를 그렇게 오래 걸리게 만드는 것은 DB 부하와도 직결되기 때문에 데이터마트를 미리 구축해서 조회하거나 다른 설계 방안을 고려해야 한다고 생각한다.
비동기라고 모두 같은 구성이 아니라 각기 다른 비동기 구성을 한다는게 너무 재밌고 흥미롭다. 나중엔 내가 직접 내 API를 비동기로 만드는 방법에 대해서 후술해보려고 한다… 조만간! (부디)
[참조]
https://notes.arkalim.org/notes/programming/asynchronous%20programming/
https://velog.io/@nittre/다스크랜투-블로킹-Vs.-논블로킹-동기-Vs.-비동기
Leave a comment